﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Threading;
using Microsoft.Practices.CompositeUI.Properties;

namespace Microsoft.Practices.CompositeUI.EventBroker
{
    /// <summary>
    /// 	Represents a topic subscription.
    /// </summary>
    internal class Subscription
    {
        #region Fields & Properties

        private readonly WorkItemSubscriptions _workItemSubscriptions;

        /// <summary>
        /// 	Maintains the subscriptions registered from a <see cref = "WorkItem" />
        /// </summary>
        public WorkItemSubscriptions WorkItemSubscriptions
        {
            get
            {
                return _workItemSubscriptions;
            }
        }

        private readonly WeakReference _weakSubscriber;

        /// <summary>
        /// 	The subscriber of the event.
        /// </summary>
        public object Subscriber
        {
            get
            {
                return _weakSubscriber.Target;
            }
        }

        private readonly string _handlerMethodName;

        /// <summary>
        /// 	The handler method name that's subscribed to the event.
        /// </summary>
        public string HandlerMethodName
        {
            get
            {
                return _handlerMethodName;
            }
        }

        private readonly ThreadOption _threadOption;

        /// <summary>
        /// 	The callback thread option for the event.
        /// </summary>
        public ThreadOption ThreadOption
        {
            get
            {
                return _threadOption;
            }
        }

        private readonly RuntimeMethodHandle _methodHandle;
        private readonly RuntimeTypeHandle _typeHandle;
        private readonly Type _handlerEventArgsType;

        private readonly SynchronizationContext _syncContext;

        #endregion

        #region Delegate Fields & Properties

        /// <summary>
        /// 	Represents the signature for the subscription objects to get called from the <see cref = "EventTopic" /> during
        /// 	a firing sequence.
        /// </summary>
        public Action<object, EventArgs, List<Exception>, TraceSource> Handler
        {
            get
            {
                return EventTopicFireHandler;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// 	Initializes a new instance of the <see cref = "Subscription" /> class.
        /// </summary>
        internal Subscription(WorkItemSubscriptions workItemSubscriptions, object subscriber, string handlerMethodName, ThreadOption threadOption) : this(workItemSubscriptions, subscriber, handlerMethodName, null, threadOption)
        {
        }

        /// <summary>
        /// 	Initializes a new instance of the <see cref = "Subscription" /> class
        /// </summary>
        internal Subscription(WorkItemSubscriptions workItemSubscriptions, object subscriber, string handlerMethodName, Type[] parameterTypes, ThreadOption threadOption)
        {
            _workItemSubscriptions = workItemSubscriptions;
            _weakSubscriber = new WeakReference(subscriber);
            _handlerMethodName = handlerMethodName;
            _threadOption = threadOption;

            var subscripterHandler = parameterTypes != null ? subscriber.GetType().GetMethod(handlerMethodName, parameterTypes) : subscriber.GetType().GetMethod(handlerMethodName);
            if (subscripterHandler == null)
            {
                throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.SubscriberHandlerNotFound, handlerMethodName, subscriber.GetType()));
            }
            if (subscripterHandler.IsStatic)
            {
                throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.CannotRegisterStaticSubscriptionMethods, subscripterHandler.DeclaringType.FullName, subscripterHandler.Name));
            }
            _typeHandle = subscriber.GetType().TypeHandle;
            _methodHandle = subscripterHandler.MethodHandle;
            var parameters = subscripterHandler.GetParameters();
            if (!(parameters.Length == 2 && typeof (EventArgs).IsAssignableFrom(parameters[1].ParameterType)))
            {
                throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.InvalidSubscriptionSignature, subscripterHandler.DeclaringType.FullName, subscripterHandler.Name));
            }
            _handlerEventArgsType = typeof (EventHandler<>).MakeGenericType(subscripterHandler.GetParameters()[1].ParameterType);
            if (threadOption == ThreadOption.UserInterface && SynchronizationContext.Current != null)
            {
                _syncContext = SynchronizationContext.Current;
            }
        }

        #endregion

        #region EventTopicFire Handler

        private void EventTopicFireHandler(object sender, EventArgs e, List<Exception> exceptions, TraceSource traceSource)
        {
            var subscriber = _weakSubscriber.Target;
            if (subscriber != null)
            {
                switch (_threadOption)
                {
                    case ThreadOption.Publisher:
                        CallOnPublisher(sender, e, exceptions);
                        break;
                    case ThreadOption.Background:
                        CallOnBackgroundWorker(sender, e, traceSource);
                        break;
                    case ThreadOption.UserInterface:
                        CallOnUserInterface(sender, e, exceptions);
                        break;
                }
            }
        }

        private void CallOnPublisher(object sender, EventArgs e, List<Exception> exceptions)
        {
            try
            {
                var handler = CreateSubscriptionDelegate();
                if (handler != null)
                {
                    handler.DynamicInvoke(sender, e);
                }
            }
            catch (TargetInvocationException ex)
            {
                exceptions.Add(ex.InnerException);
            }
        }

        private void CallOnBackgroundWorker(object sender, EventArgs e, TraceSource traceSource)
        {
            var handler = CreateSubscriptionDelegate();
            if (handler != null)
            {
                ThreadPool.QueueUserWorkItem(state =>
                                                 {
                                                     var args = (CallInBackgroundArguments) state;
                                                     try
                                                     {
                                                         args.Handler.DynamicInvoke(args.Sender, args.EventArgs);
                                                     }
                                                     catch (Exception ex)
                                                     {
                                                         args.TraceSource.TraceInformation(Resources.BackgroundSubscriberException, ex.ToString());
                                                         throw;
                                                     }
                                                 }, new CallInBackgroundArguments(sender, e, handler, traceSource));
            }
        }

        private void CallOnUserInterface(object sender, EventArgs e, List<Exception> exceptions)
        {
            var handler = CreateSubscriptionDelegate();
            if (handler != null)
            {
                if (_syncContext != null)
                {
                    _syncContext.Send(data =>
                                          {
                                              try
                                              {
                                                  ((Delegate) data).DynamicInvoke(sender, e);
                                              }
                                              catch (TargetInvocationException ex)
                                              {
                                                  exceptions.Add(ex.InnerException);
                                              }
                                          }, handler);
                }
                else
                {
                    try
                    {
                        handler.DynamicInvoke(sender, e);
                    }
                    catch (TargetInvocationException ex)
                    {
                        exceptions.Add(ex.InnerException);
                    }
                }
            }
        }

        private Delegate CreateSubscriptionDelegate()
        {
            var subscriber = _weakSubscriber.Target;
            return subscriber != null ? Delegate.CreateDelegate(_handlerEventArgsType, subscriber, (MethodInfo) MethodBase.GetMethodFromHandle(_methodHandle, _typeHandle)) : null;
        }

        #endregion

        #region CallInBackgroundArguments Stucture

        private struct CallInBackgroundArguments
        {
            #region Fields & Properties

            private readonly object _sender;

            public object Sender
            {
                get
                {
                    return _sender;
                }
            }

            private readonly EventArgs _eventArgs;

            public EventArgs EventArgs
            {
                get
                {
                    return _eventArgs;
                }
            }

            private readonly Delegate _handler;

            public Delegate Handler
            {
                get
                {
                    return _handler;
                }
            }

            private readonly TraceSource _traceSource;

            public TraceSource TraceSource
            {
                get
                {
                    return _traceSource;
                }
            }

            #endregion

            #region Constructors

            public CallInBackgroundArguments(object sender, EventArgs eventArgs, Delegate handler, TraceSource traceSource)
            {
                _sender = sender;
                _eventArgs = eventArgs;
                _handler = handler;
                _traceSource = traceSource;
            }

            #endregion
        }

        #endregion
    }
}